package game.leon.engine.data;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * User: bianpeng
 * Date: 12-2-2
 */
public class ExcelDataType<T> {
    private Logger logger = Logger.getLogger(getClass());
    //
    // 一个字段, 作为一列
    //
    private static class FieldColumn {
        Field field;
        Data anno;
        String name;    // 名称
        int index;      // 序号
    }
    private Class<T> dataType;
    private List<FieldColumn> columns;
    public ExcelDataType(Class<T> dataType) {
        this.dataType = dataType;
        columns = new ArrayList<FieldColumn>();
        recognize(dataType, columns);
    }
    private void recognize(Class<?> dataType, List<FieldColumn> columns) {
        // parent first
        Class<?> superType = dataType.getSuperclass();
        if (superType != null && superType != Object.class) {
            recognize(superType, columns);
        }
        // self
        for(Field field : dataType.getDeclaredFields()) {
            Data anno = field.getAnnotation(Data.class);
            if (anno == null) continue;
            field.setAccessible(true);
            FieldColumn column = new FieldColumn();
            column.anno = anno;
            column.field = field;
            column.name = StringUtils.isEmpty(anno.name()) ?
                    field.getName().toUpperCase(): anno.name();
            column.index = StringUtils.isEmpty(anno.value()) ? columns.size() : columnToIndex(anno.value());
            columns.add(column);
        }
    }
    //
    // excel列名转换成下标
    //
    private static int columnToIndex(String column) {
        int val = 0;
        for(int i = 0; i < column.length(); ++i) {
            int c = column.charAt(i) | 32;
            val = val * 26 + c - 'a';
        }
        return val;
    }
    //
    // 把一行读取成一个数据配置
    //
    public T make(Row row) {
        try {
            T data = dataType.newInstance();
            for(int i = 0; i < this.columns.size(); ++i) {
                // 逐项填充数据
                FieldColumn column = this.columns.get(i);
                Cell cell = row.getCell(column.index);
                if (cell == null) continue;
                setDataFieldValue(data, column, cell);
            }
            return data;
        } catch (InstantiationException e) {
            logger.error("", e);
        } catch (IllegalAccessException e) {
            logger.error("", e);
        }
        return null;
    }
    //
    // 设置数据
    //
    private static <T> void setDataFieldValue(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        switch (column.anno.format()) {
            case Data.FORMAT_JSON:
                setDataFieldJSON(data, column, cell);
                break;
            case Data.FORMAT_AUTO:
                setDataFieldAuto(data, column, cell);
                break;
            default:
                Logger.getLogger(ExcelDataType.class).error("not support data format:" + column.name);
                break;
        }
    }
    //
    // 自动读取, 这个数据需要自己来辨识, 判断, 相当困难
    //
    private static <T> void setDataFieldAuto(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        if (column.field.getType().isPrimitive()) {
            setDataFieldPrimitive(data, column, cell);
        } else if(column.field.getType() == String.class) {
            setDataFieldString(data, column, cell);
        } else {
            setDataField(data, column, cell);
        }
    }
    //
    // 给字符串字段, 不管实际内容是啥, 都转换成字符串
    //
    private static <T> void setDataFieldString(T data, FieldColumn column, Cell cell) throws IllegalAccessException {

        Field field = column.field;
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_BLANK:
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                boolean bool = cell.getBooleanCellValue();
                assert field.getType() == Boolean.class;
                field.set(data, bool ? "true" : "false");
                break;
            case Cell.CELL_TYPE_NUMERIC:
                double num = cell.getNumericCellValue();
                int decimal = (int)num;
                if (Math.abs(decimal - num) > 1e-3) {
                    field.set(data, Double.toString(num));
                } else {
                    field.set(data, Integer.toString(decimal));
                }
                break;
            case Cell.CELL_TYPE_STRING:
                field.set(data, cell.getStringCellValue());
            default:
                break;
        }
    }
    //
    // 包装类型的情况下
    //
    private static <T> void setDataField(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        Field field = column.field;
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_BLANK:
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                boolean val = cell.getBooleanCellValue();
                assert field.getType() == Boolean.class;
                field.set(data, val);
                break;
            case Cell.CELL_TYPE_NUMERIC:
                setDataFieldWrappedNumbers(data, column, cell);
                break;
            case Cell.CELL_TYPE_STRING:
                field.set(data, cell.getStringCellValue());
            default:
                break;
        }
    }

    private static <T> void setDataFieldWrappedNumbers(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        double val = cell.getNumericCellValue();
        Field field = column.field;
        Class<?> type = column.field.getType();
        if (type == Short.class) {
            field.set(data, (short) val);
        } else if (type == Integer.class) {
            field.set(data, (int) val);
        } else if (type == Long.class) {
            field.set(data, (long) val);
        } else if (type == Float.class) {
            field.set(data, (float) val);
        } else if (type == Double.class) {
            field.set(data, val);
        } else if (type == Character.class) {
            field.set(data, (char) val);
        } else if (type == Byte.class) {
            field.set(data, (byte) val);
        } else if (type == Boolean.class) {
            field.set(data, (int) val != 0);
        } else {
            Logger.getLogger(ExcelDataType.class).warn("incompatible data type: " + column.name);
        }
    }

    private static <T> void setDataFieldPrimitive(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        Field field = column.field;
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_BLANK: 
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                boolean val = cell.getBooleanCellValue();
                assert field.getType() == boolean.class;
                field.setBoolean(data, val);
                break;
            case Cell.CELL_TYPE_NUMERIC:
                setDataFieldNumbers(data, column, cell);
                break;
            case Cell.CELL_TYPE_STRING:
                throw new IllegalAccessException("unable set pritimive field to string value:" + column.name);
            default:
                break;
        }
    }
    //
    // 数字格式
    //
    private static <T> void setDataFieldNumbers(T data, FieldColumn column, Cell cell) throws IllegalAccessException {
        double val = cell.getNumericCellValue();
        Field field = column.field;
        Class<?> type = column.field.getType();
        if (type == short.class) {
            field.setShort(data, (short)val);
        } else if (type == int.class) {
            field.setInt(data, (int)val);
        } else if (type == long.class) {
            field.setLong(data, (long)val);
        } else if (type == float.class) {
            field.setFloat(data, (float)val);
        } else if (type == double.class) {
            field.setDouble(data, val);
        } else if (type == char.class) {
            field.setChar(data, (char)val);
        } else if (type == byte.class) {
            field.setByte(data, (byte)val);
        } else if (type == boolean.class) {
            field.setBoolean(data, (int)val != 0);
        } else {
            Logger.getLogger(ExcelDataType.class).warn("not supported data type: " + column.name);
        }
    }
    //
    // JSON格式的
    //
    private static <T> void setDataFieldJSON(T data, FieldColumn column, Cell cell) {
        assert cell.getCellType() == Cell.CELL_TYPE_STRING;
        String json = cell.getStringCellValue();
        try {
            Object val = FormatUtil.getInstance().getJson().readValue(json, column.field.getType());
            column.field.set(data, val);
        } catch (IOException e) {
            Logger.getLogger(ExcelDataType.class).error(column.name, e);
        } catch (IllegalAccessException e) {
            Logger.getLogger(ExcelDataType.class).error(column.name, e);
        }

    }
}
